package tools.tree;

import java.io.BufferedReader;
import java.util.ArrayList;

public class NewickParser {

	private BufferedReader in;
	private boolean hasMore=false;
	private String s;
	private int id;
	private InternalTree cur,next;
	
	public NewickParser(BufferedReader in)throws Exception{
		this.in=in;
		s=in.readLine();
		readNext();
	}
	
	public boolean hasMore(){
		return hasMore;
	}
	
	public InternalTree next()throws Exception{
		if(!hasMore){
			throw new Exception("No more trees!");
		}
		cur=next;
		readNext();
		return cur;
	}
	
	private void readNext()throws Exception{
		if(s==null){
			hasMore=false;
			next=null;
		}else{
			id=0;
			next=new InternalTree();
			ArrayList<String> list=findItems(s);
			if(list.size()==1){
				itemToNode(list.get(0), null);
			}else{
				InternalTreeNode root=new InternalTreeNode(null,id++);
				next.addNode(root);
				for (String string : list) {
					itemToNode(string, root);
				}
			}
			hasMore=true;
		}
	}
	
	
	private ArrayList<String> findItems(String t)throws Exception{
		ArrayList<String> list=new ArrayList<String>();
		String item="";
		int level=0;
		boolean inComment=false,inQLabel=false;
		for(int i=0; i<t.length();i++){
			if(inComment){
				if(t.charAt(i)==']'){
					inComment=false;
				}
			}else if(inQLabel){
				switch (t.charAt(i)) {
				case '[':
					inComment=true;
					break;
				case '\'':
					inQLabel=false;
					item+=t.charAt(i);
					break;
				default:
					item+=t.charAt(i);
					break;
				}
			}else{
				switch (t.charAt(i)) {
				case '[':
					inComment=true;
					break;
				case '\'':
					inQLabel=true;
					item+=t.charAt(i);
					break;
				case '(':
					level++;
					item+=t.charAt(i);
					break;
				case ')':
					level--;
					item+=t.charAt(i);
					break;
				case ',':
					if(level==0){
						//start new item
						list.add(item);
						item=new String("");
					}else{
						item+=t.charAt(i);
					}
					break;
				case ';':
					//break tree
					if(i<t.length()-1){
						s=t.substring(i+1);
					}else{
						s=in.readLine();
					}
					list.add(item);
					item=new String("");
					break;
				default:
					item+=t.charAt(i);
					break;
				}
			}
			if(i==t.length()-1){
				t=extendString();
			}
		}
		if(item!=null&&item.length()>0){
			list.add(item);
		}
		return list;
	}
	private String extendString()throws Exception{
		String tmp=in.readLine();
		if(tmp==null){
			tmp="";
		}
		s+=tmp;
		return s;
	}
	private void itemToNode(String t,InternalTreeNode parent)throws Exception{
		InternalTreeNode tmp = new InternalTreeNode(parent,id++);
		String sublist=t;
		int i=findLastColon(sublist);
		if(i>0){
			tmp.setBranchlength(Double.parseDouble(sublist.substring(i+1)));
			sublist=sublist.substring(0, i);
		}
		i=findLastParentesis(sublist);
		if(i>0){
			tmp.setLabel(sublist.substring(i+1));
			next.addNode(tmp);
			sublist=sublist.substring(1, i);
			//the recursive part
			for(String string : findItems(sublist)){
				itemToNode(string, tmp);
			}
		}else{
			tmp.setLabel(sublist);
			next.addNode(tmp);
		}
	}
	private int findLastParentesis(String t){
		int i=t.length()-1;
		boolean inComment=false,inLabel=false,Break=false;
		for(;i>=0;i--){
			if(inComment){
				if(t.charAt(i)=='['){
					inComment=false;
				}
			}else if(inLabel){
				switch(t.charAt(i)){
				case ']':
					inComment=true;
					break;
				case '\'':
					inLabel=false;
					break;
				}
			}else{
				switch (t.charAt(i)) {
				case ']':
					inComment=true;
					break;
				case '\'':
					inLabel=true;
					break;
				case ')':
					Break=true;
					break;
				default:
					break;
				}
				if(Break){
					break;
				}
			}
		}
		return i;
	}
	private int findLastColon(String t){
		int i=t.length()-1;
		boolean inComment=false,Break=false;
		for(;i>=0;i--){
			if(inComment){
				if(t.charAt(i)=='['){
					inComment=false;
				}
			}else{
				switch (t.charAt(i)) {
				case ']':
					inComment=true;
					break;
				case '\'':
					i=-1;
					break;
				case ')':
					i=-1;
					break;
				case ':':
					Break=true;
					break;
				default:
					break;
				}
				if(Break){
					break;
				}
			}
		}
		return i;
	}
}
